from flask import Flask, request, jsonify, send_file, render_template
from flask_cors import CORS
import os
import sqlite3
import shutil
from datetime import datetime
import json
import mimetypes
from werkzeug.utils import secure_filename
import logging
import traceback

app = Flask(__name__)
CORS(app)

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 配置
UPLOAD_FOLDER = '/bak/share'
DATABASE = 'filemanager.db'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 2 * 1024 * 1024 * 1024  # 2GB max file size

# 允许的文件扩展名 - 支持所有常见文件类型
ALLOWED_EXTENSIONS = {
    # 文档类型
    'txt', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp', 'rtf',
    # 图片类型
    'png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'tif', 'svg', 'webp', 'ico', 'psd', 'ai',
    # 音频类型
    'mp3', 'wav', 'flac', 'aac', 'ogg', 'wma', 'm4a', 'opus',
    # 视频类型
    'mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm', 'm4v', 'mpg', 'mpeg', '3gp', 'f4v',
    # 压缩文件
    'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'lzma', 'cab', 'iso',
    # 代码文件
    'py', 'js', 'html', 'css', 'json', 'xml', 'csv', 'md', 'yml', 'yaml', 'ini', 'cfg', 'conf',
    'java', 'c', 'cpp', 'h', 'hpp', 'cs', 'php', 'rb', 'go', 'rs', 'swift', 'kt', 'scala',
    'sql', 'sh', 'bat', 'ps1', 'vbs', 'lua', 'pl', 'r', 'matlab', 'm',
    # 可执行文件
    'exe', 'msi', 'dmg', 'pkg', 'deb', 'rpm', 'appimage', 'snap',
    # 字体文件
    'ttf', 'otf', 'woff', 'woff2', 'eot',
    # 数据文件
    'db', 'sqlite', 'sqlite3', 'mdb', 'accdb',
    # 其他常见类型
    'log', 'tmp', 'temp', 'bak', 'backup', 'old', 'orig', 'swp', 'cache',
    'apk', 'ipa', 'jar', 'war', 'ear'
}

def allowed_file(filename):
    """检查文件扩展名是否允许 - 现在支持所有常见文件类型"""
    if not filename or '.' not in filename:
        return False
    
    extension = filename.rsplit('.', 1)[1].lower()
    
    # 支持所有在ALLOWED_EXTENSIONS中的文件类型
    if extension in ALLOWED_EXTENSIONS:
        return True
    
    # 对于不在列表中的扩展名，也允许上传（支持所有文件类型）
    # 但排除一些可能有安全风险的系统文件类型
    dangerous_extensions = {'scr', 'com', 'pif', 'application', 'gadget', 'msc', 'hta', 'cpl', 'msp', 'jar'}
    
    if extension in dangerous_extensions:
        return False
    
    # 允许其他所有文件类型
    return True

def handle_error(error, status_code=500):
    """统一错误处理"""
    logger.error(f"Error: {str(error)}")
    logger.error(traceback.format_exc())
    return jsonify({
        'success': False, 
        'error': str(error),
        'timestamp': datetime.now().isoformat()
    }), status_code

# 确保上传目录存在
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

def init_db():
    """初始化数据库"""
    conn = sqlite3.connect(DATABASE)
    # 设置UTF-8编码支持
    conn.execute('PRAGMA encoding = "UTF-8"')
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS files (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            filename TEXT NOT NULL,
            original_filename TEXT,
            filepath TEXT NOT NULL,
            filesize INTEGER,
            upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            file_type TEXT,
            folder TEXT
        )
    ''')
    
    # 检查是否需要添加新字段（向后兼容）
    cursor.execute("PRAGMA table_info(files)")
    columns = [column[1] for column in cursor.fetchall()]
    
    if 'original_filename' not in columns:
        cursor.execute('ALTER TABLE files ADD COLUMN original_filename TEXT')
    
    if 'folder' not in columns:
        cursor.execute('ALTER TABLE files ADD COLUMN folder TEXT')
    
    conn.commit()
    conn.close()

def get_file_info(filepath):
    """获取文件信息"""
    if os.path.exists(filepath):
        stat = os.stat(filepath)
        return {
            'size': stat.st_size,
            'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
            'type': mimetypes.guess_type(filepath)[0] or 'unknown'
        }
    return None

@app.route('/')
def index():
    """主页"""
    return render_template('index.html')

@app.route('/favicon.ico')
def favicon():
    """处理favicon请求"""
    return '', 204  # 返回空内容，状态码204 No Content

@app.route('/api/files', methods=['GET'])
def list_files():
    """获取文件列表"""
    try:
        # 获取当前目录参数
        current_dir = request.args.get('dir', '').strip()
        
        # 验证目录路径安全性
        if current_dir and ('..' in current_dir or current_dir.startswith('/')):
            return handle_error('目录路径不安全', 400)
        
        # 构建目标目录路径
        if current_dir:
            target_dir = os.path.join(UPLOAD_FOLDER, current_dir)
        else:
            target_dir = UPLOAD_FOLDER
        
        if not os.path.exists(target_dir):
            logger.warning(f"Target directory does not exist: {target_dir}")
            return jsonify({
                'success': True, 
                'files': [],
                'folders': [],
                'current_dir': current_dir,
                'message': '目录不存在',
                'timestamp': datetime.now().isoformat()
            })
        
        files = []
        folders = []
        total_size = 0
        
        # 只列出当前目录的直接子项
        try:
            items = os.listdir(target_dir)
        except PermissionError:
            return handle_error('没有权限访问此目录', 403)
        
        for item in items:
            try:
                item_path = os.path.join(target_dir, item)
                relative_path = os.path.relpath(item_path, UPLOAD_FOLDER).replace('\\', '/')
                
                if os.path.isdir(item_path):
                    # 处理文件夹
                    folder_info = get_file_info(item_path)
                    if folder_info:
                        folder_data = {
                            'name': item,
                            'path': relative_path,
                            'type': 'folder',
                            'modified': folder_info['modified'],
                            'size': 0  # 文件夹大小暂时设为0
                        }
                        folders.append(folder_data)
                        
                elif os.path.isfile(item_path):
                    # 处理文件
                    file_info = get_file_info(item_path)
                    if file_info:
                        # 从数据库查询原始文件名
                        display_name = item  # 默认使用文件系统中的文件名
                        upload_time = file_info.get('upload_time', file_info['modified'])
                        
                        try:
                            conn = sqlite3.connect(DATABASE)
                            # 设置UTF-8编码支持
                            conn.execute('PRAGMA encoding = "UTF-8"')
                            cursor = conn.cursor()
                            cursor.execute('SELECT original_filename, upload_time FROM files WHERE filename = ? OR filepath = ?', (item, relative_path))
                            db_result = cursor.fetchone()
                            conn.close()
                            
                            if db_result and db_result[0]:
                                display_name = db_result[0]  # 使用原始文件名
                                if db_result[1]:
                                    upload_time = db_result[1]
                        except Exception as db_error:
                            logger.warning(f"Database query error for file {item}: {str(db_error)}")
                        
                        file_data = {
                            'name': display_name,
                            'path': relative_path,
                            'size': file_info['size'],
                            'modified': file_info['modified'],
                            'type': file_info['type'],
                            'upload_time': upload_time,
                            'system_filename': item  # 保留系统文件名用于下载等操作
                        }
                        files.append(file_data)
                        total_size += file_info['size']
                        
            except Exception as item_error:
                logger.warning(f"Error processing item {item}: {str(item_error)}")
                continue
        
        # 按名称排序
        folders.sort(key=lambda x: x['name'].lower())
        files.sort(key=lambda x: x['name'].lower())
        
        # 构建面包屑导航
        breadcrumbs = []
        if current_dir:
            parts = current_dir.split('/')
            path = ''
            for part in parts:
                if part:
                    path = path + '/' + part if path else part
                    breadcrumbs.append({
                        'name': part,
                        'path': path
                    })
        
        return jsonify({
            'success': True, 
            'files': files,
            'folders': folders,
            'current_dir': current_dir,
            'breadcrumbs': breadcrumbs,
            'total_files': len(files),
            'total_folders': len(folders),
            'total_size': total_size,
            'timestamp': datetime.now().isoformat()
        })
        
    except Exception as e:
        return handle_error(e)

@app.route('/api/upload', methods=['POST'])
def upload_file():
    """上传文件"""
    try:
        # 检查是否有文件
        if 'file' not in request.files:
            return handle_error('没有选择文件', 400)
        
        file = request.files['file']
        if file.filename == '' or file.filename is None:
            return handle_error('文件名不能为空', 400)
        
        # 验证文件扩展名
        if not allowed_file(file.filename):
            return handle_error(f'不支持的文件类型。支持的类型: {", ".join(sorted(ALLOWED_EXTENSIONS))}', 400)
        
        # 安全处理文件名
        original_filename = file.filename
        filename = secure_filename(file.filename)
        
        if not filename:
            return handle_error('文件名包含非法字符', 400)
        
        # 获取目标目录
        target_dir = request.form.get('directory', '').strip()
        if not target_dir:
            target_dir = request.form.get('folder', '').strip()  # 兼容新的参数名
        
        if target_dir:
            # 验证目录路径安全性
            if '..' in target_dir or target_dir.startswith('/'):
                return handle_error('目录路径不安全', 400)
            upload_path = os.path.join(UPLOAD_FOLDER, target_dir)
            # 确保目标目录存在
            try:
                os.makedirs(upload_path, exist_ok=True)
            except Exception as mkdir_error:
                return handle_error(f'创建目录失败: {str(mkdir_error)}', 500)
        else:
            upload_path = UPLOAD_FOLDER
        
        filepath = os.path.join(upload_path, filename)
        
        # 如果文件已存在，添加时间戳
        if os.path.exists(filepath):
            name, ext = os.path.splitext(filename)
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            filename = f"{name}_{timestamp}{ext}"
            filepath = os.path.join(upload_path, filename)
        
        # 检查磁盘空间（简单检查）
        try:
            logger.info(f"开始保存文件: {filename}, 目标路径: {filepath}")
            file.save(filepath)
            file_size = os.path.getsize(filepath)
            logger.info(f"文件保存成功: {filename}, 大小: {file_size} bytes ({file_size / (1024*1024):.2f} MB)")
        except Exception as save_error:
            logger.error(f"文件保存失败: {filename}, 错误: {str(save_error)}")
            return handle_error(f'文件保存失败: {str(save_error)}', 500)
        
        # 记录到数据库
        try:
            logger.info(f"开始记录文件到数据库: {filename}")
            conn = sqlite3.connect(DATABASE)
            # 设置UTF-8编码支持
            conn.execute('PRAGMA encoding = "UTF-8"')
            cursor = conn.cursor()
            
            # 计算相对路径
            if target_dir:
                relative_path = f"{target_dir}/{filename}"
                target_folder = target_dir
            else:
                relative_path = filename
                target_folder = ''
            
            upload_time = datetime.now().isoformat()
            
            cursor.execute(
                'INSERT INTO files (filename, original_filename, filepath, filesize, upload_time, file_type, folder) VALUES (?, ?, ?, ?, ?, ?, ?)',
                (filename, original_filename, relative_path, file_size, upload_time, mimetypes.guess_type(filepath)[0] or 'unknown', target_folder)
            )
            conn.commit()
            conn.close()
            logger.info(f"数据库记录成功: {filename}")
        except Exception as db_error:
            logger.error(f"数据库记录失败: {filename}, 错误: {str(db_error)}")
            # 数据库错误不影响文件上传成功
        
        logger.info(f"File uploaded successfully: {filename} ({file_size} bytes)")
        
        return jsonify({
            'success': True, 
            'message': '文件上传成功', 
            'filename': filename,
            'original_filename': original_filename,
            'size': file_size,
            'type': mimetypes.guess_type(filepath)[0] or 'unknown',
            'timestamp': datetime.now().isoformat()
        })
    
    except Exception as e:
        return handle_error(e)

@app.route('/api/download/<path:filename>')
def download_file(filename):
    """下载文件"""
    try:
        # 安全检查文件路径
        if '..' in filename or filename.startswith('/'):
            return handle_error('文件路径不安全', 400)
        
        filepath = os.path.join(UPLOAD_FOLDER, filename)
        
        if not os.path.exists(filepath):
            return handle_error('文件不存在', 404)
        
        if not os.path.isfile(filepath):
            return handle_error('指定路径不是文件', 400)
        
        # 从数据库查询原始文件名
        original_filename = filename  # 默认使用系统文件名
        try:
            conn = sqlite3.connect(DATABASE)
            # 设置UTF-8编码支持
            conn.execute('PRAGMA encoding = "UTF-8"')
            cursor = conn.cursor()
            
            # 查询原始文件名，支持按文件名或文件路径查询
            cursor.execute('SELECT original_filename FROM files WHERE filename = ? OR filepath = ?', (filename, filename))
            db_result = cursor.fetchone()
            
            if db_result and db_result[0]:
                original_filename = db_result[0]
                logger.info(f"Found original filename in database: {original_filename}")
            else:
                logger.warning(f"Original filename not found in database for: {filename}")
            
            conn.close()
        except Exception as db_error:
            logger.error(f"Database query failed for filename {filename}: {str(db_error)}")
            # 数据库查询失败时使用系统文件名
        
        logger.info(f"File downloaded: {filename} (original: {original_filename})")
        
        # 使用原始文件名进行下载
        return send_file(filepath, as_attachment=True, download_name=original_filename)
        
    except Exception as e:
        return handle_error(e)

@app.route('/api/delete/<path:filename>', methods=['DELETE'])
def delete_file(filename):
    """删除文件"""
    try:
        # 安全检查文件路径
        if '..' in filename or filename.startswith('/'):
            return handle_error('文件路径不安全', 400)
        
        filepath = os.path.join(UPLOAD_FOLDER, filename)
        
        if not os.path.exists(filepath):
            return handle_error('文件不存在', 404)
        
        file_type = 'file' if os.path.isfile(filepath) else 'directory'
        file_size = 0
        
        try:
            if os.path.isfile(filepath):
                file_size = os.path.getsize(filepath)
                os.remove(filepath)
            elif os.path.isdir(filepath):
                # 计算目录大小
                for root, dirs, files in os.walk(filepath):
                    for file in files:
                        file_size += os.path.getsize(os.path.join(root, file))
                shutil.rmtree(filepath)
            else:
                return handle_error('未知的文件类型', 400)
        except PermissionError:
            return handle_error('没有权限删除此文件', 403)
        except Exception as delete_error:
            return handle_error(f'删除失败: {str(delete_error)}', 500)
        
        # 从数据库删除记录
        try:
            conn = sqlite3.connect(DATABASE)
            # 设置UTF-8编码支持
            conn.execute('PRAGMA encoding = "UTF-8"')
            cursor = conn.cursor()
            cursor.execute('DELETE FROM files WHERE filepath = ? OR filepath LIKE ?', 
                         (filepath, filepath + '/%'))
            deleted_count = cursor.rowcount
            conn.commit()
            conn.close()
        except Exception as db_error:
            logger.warning(f"Database cleanup error: {str(db_error)}")
            deleted_count = 0
        
        logger.info(f"{file_type.capitalize()} deleted: {filename} ({file_size} bytes)")
        
        return jsonify({
            'success': True, 
            'message': f'{file_type}删除成功',
            'filename': filename,
            'type': file_type,
            'size': file_size,
            'db_records_deleted': deleted_count,
            'timestamp': datetime.now().isoformat()
        })
        
    except Exception as e:
        return handle_error(e)

@app.route('/api/mkdir', methods=['POST'])
def create_directory():
    """创建目录（兼容旧API）"""
    try:
        data = request.get_json()
        if not data:
            return handle_error('请求数据格式错误', 400)
        
        dirname = data.get('dirname', '').strip()
        parent_dir = data.get('parent', '').strip()
        
        if not dirname:
            return handle_error('目录名不能为空', 400)
        
        # 验证目录名安全性
        if '..' in dirname or '/' in dirname or '\\' in dirname:
            return handle_error('目录名包含非法字符', 400)
        
        if parent_dir:
            if '..' in parent_dir or parent_dir.startswith('/'):
                return handle_error('父目录路径不安全', 400)
            target_path = os.path.join(UPLOAD_FOLDER, parent_dir, dirname)
        else:
            target_path = os.path.join(UPLOAD_FOLDER, dirname)
        
        if os.path.exists(target_path):
            return handle_error('目录已存在', 409)
        
        os.makedirs(target_path, exist_ok=False)
        logger.info(f"Directory created: {target_path}")
        
        return jsonify({
            'success': True, 
            'message': '目录创建成功',
            'dirname': dirname,
            'path': os.path.relpath(target_path, UPLOAD_FOLDER).replace('\\', '/'),
            'timestamp': datetime.now().isoformat()
        })
    
    except Exception as e:
        return handle_error(e)

@app.route('/api/create_folder', methods=['POST'])
def create_folder():
    """创建文件夹（新API）"""
    try:
        data = request.get_json()
        if not data:
            return handle_error('请求数据格式错误', 400)
        
        folder_name = data.get('name', '').strip()
        parent_dir = data.get('parent_dir', '').strip()
        
        if not folder_name:
            return handle_error('文件夹名不能为空', 400)
        
        # 验证文件夹名安全性
        if not folder_name or '..' in folder_name or '/' in folder_name or '\\' in folder_name:
            return handle_error('文件夹名包含非法字符', 400)
        
        if parent_dir:
            if '..' in parent_dir or parent_dir.startswith('/'):
                return handle_error('父目录路径不安全', 400)
            
            # 验证父目录是否存在
            parent_path = os.path.join(UPLOAD_FOLDER, parent_dir)
            if not os.path.exists(parent_path) or not os.path.isdir(parent_path):
                return handle_error('父目录不存在', 404)
            
            target_path = os.path.join(UPLOAD_FOLDER, parent_dir, folder_name)
        else:
            target_path = os.path.join(UPLOAD_FOLDER, folder_name)
        
        if os.path.exists(target_path):
            return handle_error('文件夹已存在', 409)
        
        os.makedirs(target_path, exist_ok=False)
        logger.info(f"Folder created: {target_path}")
        
        return jsonify({
            'success': True, 
            'message': '文件夹创建成功',
            'name': folder_name,
            'path': os.path.relpath(target_path, UPLOAD_FOLDER).replace('\\', '/'),
            'parent_dir': parent_dir,
            'timestamp': datetime.now().isoformat()
        })
    
    except Exception as e:
        return handle_error(e)

@app.route('/api/file/<path:filename>')
def view_file(filename):
    """查看文件内容"""
    try:
        # 安全检查文件路径
        if '..' in filename or filename.startswith('/'):
            return handle_error('文件路径不安全', 400)
        
        filepath = os.path.join(UPLOAD_FOLDER, filename)
        
        if not os.path.exists(filepath):
            return handle_error('文件不存在', 404)
        
        if not os.path.isfile(filepath):
            return handle_error('指定路径不是文件', 400)
        
        # 获取文件信息
        file_size = os.path.getsize(filepath)
        mime_type, encoding = mimetypes.guess_type(filepath)
        
        # 检查文件大小（限制预览大小）
        max_preview_size = 1024 * 1024  # 1MB
        if file_size > max_preview_size:
            return jsonify({
                'success': True,
                'type': 'large_file',
                'message': f'文件过大（{file_size} bytes），请下载查看',
                'size': file_size,
                'mime_type': mime_type or 'unknown',
                'timestamp': datetime.now().isoformat()
            })
        
        # 检查是否为文本文件
        text_types = ['text/', 'application/json', 'application/xml', 'application/javascript']
        is_text = mime_type and any(mime_type.startswith(t) for t in text_types)
        
        if is_text:
            try:
                # 尝试多种编码
                encodings = ['utf-8', 'gbk', 'gb2312', 'latin-1']
                content = None
                used_encoding = None
                
                for enc in encodings:
                    try:
                        with open(filepath, 'r', encoding=enc) as f:
                            content = f.read()
                        used_encoding = enc
                        break
                    except UnicodeDecodeError:
                        continue
                
                if content is not None:
                    logger.info(f"File viewed: {filename} ({used_encoding})")
                    return jsonify({
                        'success': True,
                        'content': content,
                        'type': 'text',
                        'encoding': used_encoding,
                        'mime_type': mime_type,
                        'size': file_size,
                        'timestamp': datetime.now().isoformat()
                    })
                else:
                    return jsonify({
                        'success': True,
                        'type': 'binary',
                        'message': '无法解码文件内容，请下载查看',
                        'mime_type': mime_type or 'unknown',
                        'size': file_size,
                        'timestamp': datetime.now().isoformat()
                    })
                    
            except Exception as read_error:
                return handle_error(f'读取文件失败: {str(read_error)}', 500)
        else:
            return jsonify({
                'success': True,
                'type': 'binary',
                'message': '二进制文件，请下载查看',
                'mime_type': mime_type or 'unknown',
                'size': file_size,
                'timestamp': datetime.now().isoformat()
            })
    
    except Exception as e:
        return handle_error(e)

# 添加错误处理器
@app.errorhandler(413)
def too_large(e):
    return handle_error('文件太大，超过最大上传限制', 413)

@app.errorhandler(404)
def not_found(e):
    return handle_error('请求的资源不存在', 404)

@app.errorhandler(500)
def internal_error(e):
    return handle_error('服务器内部错误', 500)

if __name__ == '__main__':
    init_db()
    app.run(host='0.0.0.0', port=5000, debug=True)